package com.study.service.impl;

import com.study.config.LicenceInterceptor;
import com.study.constant.CommonKeys;
import com.study.entity.LicenceEntity;
import com.study.enums.LicenceEnum;
import com.study.service.LicenceCheckService;
import com.study.util.LicenceSecurityUtil;
import com.study.util.LicenceDateUtil;
import com.study.util.LicenceJsonUtil;
import com.study.util.MachineAddrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @author CSDN 流放深圳
 * @description 校验证书接口实现类
 * 1、证书存放的地方要在程序的 jar 包外，通过读取外部文件的形式来判断证书的有效性，这样可以不用重启服务来更换证书。
 * 2、提醒频率：到期前 7 天开始提醒，每隔 12 小时提醒一次即可。
 * 3、定时器：每 10 分钟执行一次，判断证书是否有效。并且把是否有效这个标识存入系统缓存，给拦截器使用。
 * @create 2024-04-13 15:55
 * @since 1.0.0
 */
@Service
public class LicenceCheckServiceImpl implements LicenceCheckService {

    private static Logger log = LoggerFactory.getLogger(LicenceCheckServiceImpl.class);

    private final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 校验证书
     *
     * @return
     */
    @Override
    public Boolean checkLicence() {
        try {
            //读取指定目录下的证书文件
            String fileContent = readLicenceFile();
            if (fileContent == null || fileContent.isEmpty()) {
                //设置拦截器的枚举类
                LicenceInterceptor.licenceEnum = LicenceEnum.NOT_FOUND;
                return false;
            }

            //转为具体对象
            LicenceEntity fileEntity = LicenceJsonUtil.strToObject(fileContent, LicenceEntity.class);
            if (null == fileEntity) {
                LicenceInterceptor.licenceEnum = LicenceEnum.NOT_FOUND;
                return false;
            }
            //获取 content
            String content = fileEntity.getContent();
            if (content == null || content.isEmpty()) {
                LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;
                return false;
            }

            //获取公钥
            String publicKey = fileEntity.getKey();
            if(publicKey == null || publicKey.isEmpty()){
                LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;
                return false;
            }

            //解密内容，这是重点字段，这个字段是将其它字段加密后的完整字段
            String decryptStr = LicenceSecurityUtil.decryptByPublicKey(content, publicKey);
            if(decryptStr == null || decryptStr.isEmpty()){
                LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;
                return false;
            }
            LicenceEntity decryptEntity = LicenceJsonUtil.strToObject(decryptStr, LicenceEntity.class);
            //判断机器的 Mac 地址是否匹配（此项校验看业务场景的严格程度，如果生成 Licence 时就没有输入 Mac，则不用判断。如果要求严格，则生成 Licence 时就必填）
            Boolean macFlag = checkMac(decryptEntity);
            if(!macFlag){
                LicenceInterceptor.licenceEnum = LicenceEnum.ILLEGALLY_STOLEN;
                return false;
            }

            //判断时间的合理性（重点）
            Boolean effectFlag = checkDateEffect(decryptEntity);
            if (effectFlag) {
                LicenceInterceptor.licenceEnum = LicenceEnum.SUCCESS;
                return true;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;//一旦抛异常，则表示证书无效
            return false;
        }
        return false;
    }

    /**
     * 判断机器的 Mac 地址是否匹配（此项非必须，如果生成 Licence 时就没有输入 Mac，则不用判断）
     * @param entity
     * @return
     */
    private Boolean checkMac(LicenceEntity entity){
        String mac = entity.getMac();
        if(mac == null || mac.isEmpty()) {//密文里没有 mac 地址证明不需要校验，直接放行
            return true;
        }else{
            //获取当前机器的 mac
            String machineMac = MachineAddrUtil.getMac();
            if(machineMac == null || machineMac.isEmpty()){
                return false;
            }
            if(!mac.equals(machineMac)){//不相等，说明被盗用
                return false;
            }
        }
        return true;
    }

    /**
     * 判断时间的合理性
     * 严格点的话，也可以判断证书id和其它字段，来判断证书内容是否被篡改。
     * @param entity
     * @return
     */
    private Boolean checkDateEffect(LicenceEntity entity) {
        if (null == entity) {
            LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;
            return false;
        }
        //判断时间的合理性
        String effectStartDate = entity.getEffectStartDate();
        String effectEndDate = entity.getEffectEndDate();
        if(effectStartDate == null || effectStartDate.isEmpty() || effectEndDate == null || effectEndDate.isEmpty()){
            LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;
            return false;
        }
        try {
            Date startDate = SDF.parse(effectStartDate);
            //如果是长期有效
            if (CommonKeys.LONG_TERM.equals(effectEndDate)) {
                effectEndDate = CommonKeys.LONG_TERM_END_DATE;
            }
            Date endDate = SDF.parse(effectEndDate);
            Date now = new Date();//服务器当前日期
            //判断证书是否已过期。0=相等，即将到期；-1=已过期（结束日期比现在小）；1=未过期
            long compare = LicenceDateUtil.getDistanceOfTwoDate(now, endDate);
            if (compare < 0) {//已过期
                LicenceInterceptor.licenceEnum = LicenceEnum.EXPIRE;
                return false;
            } else {//可以根据时间差，做一些业务逻辑，比如7天前就开始发消息通知等等
                notifyTo(compare, endDate, now);
            }
            boolean inFlag = LicenceDateUtil.isIn(startDate, endDate, now);
            if (!inFlag) {//许可证书未在有效的授权时间内
                LicenceInterceptor.licenceEnum = LicenceEnum.OUT_OF_TIME;
                return false;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            LicenceInterceptor.licenceEnum = LicenceEnum.INVALID;
            return false;
        }
        return true;
    }

    /**
     * 读取指定目录下的证书文件
     *
     * @return
     */
    private String readLicenceFile() throws Exception {
        String result = null;
        // 创建证书目录（如果尚未创建）
        Path directory = Paths.get(CommonKeys.CERTIFICATE_DIRECTORY);
        Files.createDirectories(directory);
        // 构建证书文件的完整路径
        Path filePath = directory.resolve(CommonKeys.CERTIFICATE_FILE);
        // 检查文件是否存在
        if (Files.exists(filePath)) {
            // 读取文件内容
            List<String> lines = Files.readAllLines(filePath);
            if(!lines.isEmpty() && lines.size() > 0){
                result = lines.get(0);//只读取第一行，即数据行
            }
        }
        return result;
    }

    /**
     * 根据证书到期时间，做一些通知
     *
     * @param compare
     * @param endDate
     * @param now
     */
    @Override
    public void notifyTo(long compare, Date endDate, Date now) {
        if (compare == 0) {
            log.info("Your licence will be expired today. To avoid affecting normal business operations, please replace it with a new licence in a timely manner! 您的授权证书今日到期，为避免影响业务正常进行，请您及时更换新证书！");
        } else if (compare >= 1) {
            //两个日期做对比
            long remainingDay = LicenceDateUtil.getDistanceOfTwoDate(endDate, now);
            if (remainingDay > 0 && remainingDay <= CommonKeys.REMIND_DATE) {
                log.info("Your licence still has " + remainingDay + " days to expire.您的授权证书还有 " + remainingDay + " 天到期。");
            }
        }
    }

}